﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using PSE.Updater.Common.Providers;
using PSE.Framework.ErrorLogging;

namespace PSE.Updater.Server.Engine
{
    public class RegistryProduct
    {
        private const string CONST_XML_UPDATE_PROVIDERS = "updateProviders";
        private const string CONST_XML_UPDATE_CONFIGURATION = "configuration";
        private const string CONST_XML_TAG_UPDATE_PROVIDERS = "<" + CONST_XML_UPDATE_PROVIDERS + ">";
        private const string CONST_XML_TAG_UPDATE_PROVIDERS_CLOSE = "</" + CONST_XML_UPDATE_PROVIDERS + ">";
        private const string CONST_XML_TAG_UPDATE_PROVIDERS_NS = "<" + CONST_XML_UPDATE_PROVIDERS + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">";

        private string _appConfigFile = string.Empty;
        private UpdateProvider _updateProvider = null;

        private RegistryProduct()
        {
            _appConfigFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            _updateProvider = LoadUpdateProviders();
        }

        private static object _lock = new object();
        private static RegistryProduct _instance;
        public static RegistryProduct Instance
        {
            get
            {
                lock (_lock)
                {
                    if (_instance == null)
                        _instance = new RegistryProduct();

                    return _instance;
                }
            }
        }

        #region [ XML ]
        private UpdateProvider Deserialize(string xml)
        {
            try
            {
                XmlSerializer s = new XmlSerializer(typeof(UpdateProvider));

                StringReader sr = new StringReader(xml);
                return s.Deserialize(sr) as UpdateProvider;
            }
            catch (Exception ex)
            {
                ErrorLogger.WriteLog(LogType.Error, ex);
                throw;
            }
        }

        private string Serialize(UpdateProvider updateProvider)
        {
            try
            {
                XmlSerializer s = new XmlSerializer(typeof(UpdateProvider));

                StringWriter sw = new StringWriter();
                s.Serialize(sw, updateProvider);
                string serializedXml = sw.ToString();

                return serializedXml;
            }
            catch (Exception ex)
            {
                ErrorLogger.WriteLog(LogType.Error, ex);
                throw;
            }
        }

        private void FormatXml(ref string xml)
        {
            xml = xml.Replace(CONST_XML_TAG_UPDATE_PROVIDERS, CONST_XML_TAG_UPDATE_PROVIDERS_NS);
        }

        private void Unformat(ref string xml)
        {
            xml = xml.Replace(CONST_XML_TAG_UPDATE_PROVIDERS_NS, CONST_XML_TAG_UPDATE_PROVIDERS);
            if (xml.StartsWith("<?xml"))
            {
                int index = xml.IndexOf('\n');
                xml = xml.Remove(0, index + 1);
            }
        }

        private UpdateProvider LoadUpdateProviders()
        {
            lock (_lock)
            {

                try
                {
                    UpdateProvider updateProvider = null;
                    if (_updateProvider == null)
                    {
                        XDocument root = XDocument.Load(_appConfigFile);
                        var updateProvidersXml = root.Descendants(CONST_XML_UPDATE_PROVIDERS);
                        if (updateProvidersXml.Count() > 0)
                        {
                            var updateProviderXml = updateProvidersXml.FirstOrDefault();
                            var xml = updateProviderXml.ToString();
                            FormatXml(ref xml);

                            updateProvider = Deserialize(xml);
                        }
                        else
                        {
                            updateProvider = new UpdateProvider();
                        }
                    }
                    return updateProvider;
                }
                catch (Exception ex)
                {
                    ErrorLogger.WriteLog(LogType.Error, ex);
                    throw;
                }
            }
        }
        #endregion [ XML ]

        public void Save()
        {
            string xml = Serialize(_updateProvider);
            Unformat(ref xml);

            XDocument document = XDocument.Load(_appConfigFile);
            var updateProviders = document.Descendants(CONST_XML_UPDATE_PROVIDERS);

            if (updateProviders.Count() > 0)
            {
                var updateProvider = updateProviders.FirstOrDefault();
                updateProvider.Remove();
            }

            XElement newUpdateProvider = XElement.Parse(xml);
            var configurations = document.Descendants(CONST_XML_UPDATE_CONFIGURATION);
            if (configurations.Count() > 0)
            {
                var configuration = configurations.FirstOrDefault();
                configuration.Add(newUpdateProvider);
                document.Save(_appConfigFile);
            }

        }

        public void AddProduct(string productName)
        {
            var product = _updateProvider.Products.FirstOrDefault(p => p.Name.Equals(productName, StringComparison.InvariantCultureIgnoreCase));
            if (product == null)
            {
                product = new Product { Name = productName };
                _updateProvider.Products.Add(product);
            }
        }

        public void RemoveProduct(string productName)
        {
            var product = _updateProvider.Products.FirstOrDefault(p => p.Name.Equals(productName, StringComparison.InvariantCultureIgnoreCase));
            if (product != null)
                _updateProvider.Products.Remove(product);
        }

        public void AddFolder(ProviderType providerType, string productName, string folder)
        {
            var product = _updateProvider.Products.FirstOrDefault(p => p.Name.Equals(productName, StringComparison.InvariantCultureIgnoreCase));
            if (product == null)
                throw new ArgumentNullException("product");

            bool canAdd = false;
            var provider = product.Providers.FirstOrDefault(p => p.Type == providerType);
            if (provider == null)
            {
                canAdd = true;
                provider = new Provider { Type = providerType };
            }

            string extensionsFilter = (providerType == ProviderType.Updates) ? "*.exe" : "*.config";
            provider.Watchers.Add(new Folder
            {
                Directory = folder,
                ExtensionsFilter = extensionsFilter,
                Recursive = true,
                SilentMode = true
            });

            if (canAdd)
                product.Providers.Add(provider);
        }

        public void AlterFolder(ProviderType providerType, string productName, string oldFolder, string newFolder)
        {
            var product = _updateProvider.Products.FirstOrDefault(p => p.Name.Equals(productName, StringComparison.InvariantCultureIgnoreCase));
            if (product == null)
                throw new ArgumentNullException("product");

            var provider = product.Providers.FirstOrDefault(p => p.Type == providerType);
            if (provider == null)
                throw new ArgumentNullException("provider");

            var folder = RemoveFolder(providerType, productName, oldFolder);
            provider.Watchers.Add(new Folder
            {
                Directory = newFolder,
                ExtensionsFilter = folder.ExtensionsFilter,
                Recursive = folder.Recursive,
                SilentMode = folder.SilentMode
            });
        }

        public Folder RemoveFolder(ProviderType providerType, string productName, string folderPath)
        {
            var product = _updateProvider.Products.FirstOrDefault(p => p.Name.Equals(productName, StringComparison.InvariantCultureIgnoreCase));
            if (product == null)
                throw new ArgumentNullException("product");

            var provider = product.Providers.FirstOrDefault(p => p.Type == providerType);
            if (provider == null)
                throw new ArgumentNullException("provider");

            var folder = provider.Watchers.FirstOrDefault(f => f.Directory.Equals(folderPath, StringComparison.InvariantCultureIgnoreCase));
            if (folder == null)
                throw new ArgumentNullException("folder");

            provider.Watchers.Remove(folder);
            if (provider.Watchers.Count == 0)
                product.Providers = null;

            return folder;
        }

        public void RemoveAllFolders(ProviderType providerType, string productName)
        {
            var product = _updateProvider.Products.FirstOrDefault(p => p.Name.Equals(productName, StringComparison.InvariantCultureIgnoreCase));
            if (product == null)
                throw new ArgumentNullException("product");

            var provider = product.Providers.FirstOrDefault(p => p.Type == providerType);
            if (provider == null)
                throw new ArgumentNullException("provider");

            provider.Watchers.RemoveAll(p => true);
            product.Providers = null;
        }
    }

    #region [ Serializable Classes ]
    [XmlRoot("updateProviders")]
    public class UpdateProvider
    {
        [XmlArray("products"), XmlArrayItem(ElementName = "product", Type = typeof(Product))]
        public List<Product> Products { get; set; }

        public UpdateProvider()
        {
            this.Products = new List<Product>();
        }
    }

    [XmlRoot("product")]
    public class Product
    {
        [XmlAttribute("name")]
        public string Name { get; set; }

        [XmlArray("providers"), XmlArrayItem(ElementName = "provider", Type = typeof(Provider))]
        public List<Provider> Providers { get; set; }

        public Product()
        {
            this.Providers = new List<Provider>();
        }
    }

    [XmlRoot("provider")]
    public class Provider
    {
        [XmlAttribute("type")]
        public ProviderType Type { get; set; }

        [XmlArray("watchers"), XmlArrayItem(ElementName = "watcher", Type = typeof(Folder))]
        public List<Folder> Watchers { get; set; }

        public Provider()
        {
            this.Watchers = new List<Folder>();
        }
    }

    [XmlRoot("watcher")]
    public class Folder
    {
        [XmlAttribute("directory")]
        public string Directory { get; set; }
        [XmlAttribute("silentMode")]
        public bool SilentMode { get; set; }
        [XmlAttribute("extensionsFilter")]
        public string ExtensionsFilter { get; set; }
        [XmlAttribute("recursive")]
        public bool Recursive { get; set; }
    }
    #endregion [ Serializable Classes ]
}